#include<stdio.h>
#include<curl/curl.h>  //apt install libcurl4、3-gnutls-dev  安装后提供对应的文件
#include <sys/types.h>
#include <string.h>
#include<stdlib.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>

struct fileInfo{
	char * ptr;
	int offset;
};


//long getDownLoadFileLeng(const char * url)
double getDownLoadFileLeng(const char * url)
{
	double length=0;  //content length is double,can't use long to rcv
	CURL * curl=curl_easy_init();
	//set curl obj charact
	curl_easy_setopt(curl,CURLOPT_URL,url);

	//set curl obj charact,indicate req header info
	curl_easy_setopt(curl,CURLOPT_HEADER,1);
	curl_easy_setopt(curl,CURLOPT_NOBODY,1);	//only req header no body

	CURLcode res = curl_easy_perform(curl);
	if(res==CURLE_OK)
	{
		curl_easy_getinfo(curl,CURLINFO_CONTENT_LENGTH_DOWNLOAD,&length);
	}else
	{
		printf("download len err\n");
		length=-1;
	}
	curl_easy_cleanup(curl);
	printf("leng=%lf\n",length);
	return length;
}

//every time,recv 16k byte data,need write to file
size_t writeFunc(void *netptr,size_t size,size_t memb,void* userdata)
{
	printf("in writeFunc,size=%ld\n",size*memb);
#if 0
	// this method err,why  这种写法会出错.
	memcpy(((struct fileInfo *)userdata)->ptr+((struct fileInfo *)userdata)->offset ,netptr,size*memb);
	((struct fileInfo*)userdata)->offset+=size*memb;
#endif
	struct fileInfo * info=(struct fileInfo*)userdata; //将指针类型转换赋值到别的变量再去操作就好了
	memcpy(info->ptr+info->offset,netptr,size*memb);
	info->offset+=size*memb;
	printf("out write fun\n");
	return size*memb;
}

//use libcurl to download
int download(const char*url,const char *filename)
{
	long length=0;
	length=getDownLoadFileLeng(url);//可以先获取文件大小,根据文件大小创建对应大小的文件
	if(length==-1)
	{
		printf("get len err\n");
		return -1;
	}
	//int fd=open(filename,O_RDWR|O_CREAT);  create like this,
	//can't user mult thread to save data,because only can write to tail position
	int fd=open(filename,O_RDWR|O_CREAT,S_IRUSR|S_IWUSR);//add file Thrones
	if(fd==-1) {
		printf("open failed\n");	
		return -1;
	}
	if (-1==lseek(fd,length-1,SEEK_SET))	//resize fd size,so can wirte mult thread
	{
		perror("can not set file size\n");
		goto lseekerr;
	}
#if 1	写个空字符后,ls -l 可以看到文件大小改变了,不写空字符 则文件大小还是0 
	// write then file's size is init ok,no write then size==0
	if(1!=write(fd,"",1))	
	{
		perror("write");
		close(fd);
		return -1;
	}
#endif
	char *ptr= mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	if(ptr==MAP_FAILED)
	{
		printf("mmap err\n");
		goto maperr;
	}
#if 1
	struct fileInfo * info=(struct fileInfo*)malloc(sizeof(struct fileInfo));
	info->ptr=ptr;
	info->offset=0;
	//init curl object
	CURL * curl=curl_easy_init();
	//set curl obj charact
	curl_easy_setopt(curl,CURLOPT_URL,url);

	//set curl obj charact
	curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,writeFunc);	//在回调函数 writeFunc 中,将收到的数据 写进文件
															//由于是curl是基于tcp的,则网络发回来的数据一定是从头到尾的,所以回调
															//函数中,每次写完后 offset ++,即可
	curl_easy_setopt(curl,CURLOPT_WRITEDATA,info);//put ptr to writeFunc para

	//exec while(1) util download over--waste time
	CURLcode res = curl_easy_perform(curl);
	if(res!=CURLE_OK)
	{
		printf("res not ok %d\n",res);
	}
	curl_easy_cleanup(curl);
	munmap(ptr,length);//unmap
	return 0;
#endif
maperr:
lseekerr:
	close(fd);
	return -3;
}

int main(int argc,const char * argv[])
{
	download(argv[1],argv[2]);
}

多线程下载的方法思路:
http的请求可以分段请求,可以创建10个线程,网络请求的时候 将目标文件也分成10段去请求.
往目标文件写的时候,分别写到目标文件对应位置.--每个线程写固定的数据段

本例程写的时候:若目标文件几个G,则会运行失败,猜想linux系统内存太小--但是变大内存后还是失败.50M 以下的小文件可以运行成功